﻿using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Collections.ObjectModel;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework;
using System.Reflection;
using System.Runtime.InteropServices;

namespace Oops.Xna.Framework.Graphics
{
	public abstract class InstancedData
	{
		#region InstancedDataProperty Declaration

		public abstract class InstancedDataProperty
		{
			private string name;
			private Type targetType;

			internal InstancedDataProperty(string name, Type targetType)
			{
				this.name = name;
				this.targetType = targetType;
			}

			public string Name
			{
				get { return this.name; }
			}

			public Type TargetType
			{
				get { return this.targetType; }
			}

			protected internal abstract int SizeOf { get; }

			protected internal abstract object GetValue();
			protected internal abstract void SetValue(object value);
			protected internal abstract void SetEffectParameterValue(EffectParameter parameter, object value);
			protected internal abstract VertexElement[] GetVertexDeclaration(short stream, ref byte usageIndex);
			protected internal abstract void SetDataStream(DynamicVertexBuffer stream);
		}

		#endregion

		#region InstancedDataProperty<T> Declaration

		public abstract class InstancedDataProperty<T> : InstancedDataProperty where T : struct
		{
			private int sizeOf;

			internal InstancedDataProperty(string name) : base(name, typeof(T[])) 
			{
				this.sizeOf = Marshal.SizeOf(default(T));
			}

			protected internal override int SizeOf
			{
				get { return this.sizeOf; }
			}

			protected internal override void SetValue(object value)
			{
				this.Value = (T[])value;
			}

			protected internal override object GetValue()
			{
				return this.Value;
			}

			public T[] Value { get; set; }

			protected internal override void SetDataStream(DynamicVertexBuffer stream)
			{
				stream.SetData<T>(this.Value, 0, this.Value.Length, SetDataOptions.Discard);
			}

			protected internal override void SetEffectParameterValue(EffectParameter parameter, object value)
			{
				this.SetEffectParameterValue(parameter, (T[])value);
			}

			protected internal abstract void SetEffectParameterValue(EffectParameter parameter, T[] value);
		}

		#endregion

		#region QuaternionInstancedDataProperty Declaration

		internal class QuaternionInstancedDataProperty : InstancedDataProperty<Quaternion>
		{
			internal QuaternionInstancedDataProperty(string name) : base(name) { }

			protected internal override VertexElement[] GetVertexDeclaration(short stream, ref byte usageIndex)
			{
				VertexElement[] result = new VertexElement[1];

				result[0] = new VertexElement(
					stream,
					0,
					VertexElementFormat.Vector4,
					VertexElementMethod.Default,
					VertexElementUsage.TextureCoordinate,
					usageIndex++);

				return result;
			}

			protected internal override void SetEffectParameterValue(EffectParameter parameter, Quaternion[] value)
			{
				parameter.SetValue(value);
			}
		}

		#endregion

		#region Vector2InstancedDataProperty Declaration

		internal class Vector2InstancedDataProperty : InstancedDataProperty<Vector2>
		{
			internal Vector2InstancedDataProperty(string name) : base(name) { }

			protected internal override VertexElement[] GetVertexDeclaration(short stream, ref byte usageIndex)
			{
				VertexElement[] result = new VertexElement[1];

				result[0] = new VertexElement(
					stream,
					0,
					VertexElementFormat.Vector2,
					VertexElementMethod.Default,
					VertexElementUsage.TextureCoordinate,
					usageIndex++);

				return result;
			}

			protected internal override void SetEffectParameterValue(EffectParameter parameter, Vector2[] value)
			{
				parameter.SetValue(value);
			}
		}

		#endregion

		#region Vector3InstancedDataProperty Declaration

		internal class Vector3InstancedDataProperty : InstancedDataProperty<Vector3>
		{
			internal Vector3InstancedDataProperty(string name) : base(name) { }

			protected internal override VertexElement[] GetVertexDeclaration(short stream, ref byte usageIndex)
			{
				VertexElement[] result = new VertexElement[1];

				result[0] = new VertexElement(
					stream,
					0,
					VertexElementFormat.Vector3,
					VertexElementMethod.Default,
					VertexElementUsage.TextureCoordinate,
					usageIndex++);

				return result;
			}

			protected internal override void SetEffectParameterValue(EffectParameter parameter, Vector3[] value)
			{
				parameter.SetValue(value);
			}
		}

		#endregion

		#region Vector4InstancedDataProperty Declaration

		internal class Vector4InstancedDataProperty : InstancedDataProperty<Vector4>
		{
			internal Vector4InstancedDataProperty(string name) : base(name) { }

			protected internal override VertexElement[] GetVertexDeclaration(short stream, ref byte usageIndex)
			{
				VertexElement[] result = new VertexElement[1];

				result[0] = new VertexElement(
					stream,
					0,
					VertexElementFormat.Vector4,
					VertexElementMethod.Default,
					VertexElementUsage.TextureCoordinate,
					usageIndex++);

				return result;
			}

			protected internal override void SetEffectParameterValue(EffectParameter parameter, Vector4[] value)
			{
				parameter.SetValue(value);
			}
		}

		#endregion

		#region BooleanInstancedDataProperty Declaration

		internal class BooleanInstancedDataProperty : InstancedDataProperty<bool>
		{
			internal BooleanInstancedDataProperty(string name) : base(name) { }

			protected internal override VertexElement[] GetVertexDeclaration(short stream, ref byte usageIndex)
			{
				VertexElement[] result = new VertexElement[1];

				result[0] = new VertexElement(
					stream,
					0,
					VertexElementFormat.Single,
					VertexElementMethod.Default,
					VertexElementUsage.TextureCoordinate,
					usageIndex++);

				return result;
			}

			protected internal override void SetEffectParameterValue(EffectParameter parameter, bool[] value)
			{
				parameter.SetValue(value);
			}
		}

		#endregion

		#region Int32InstancedDataProperty Declaration

		internal class Int32InstancedDataProperty : InstancedDataProperty<int>
		{
			internal Int32InstancedDataProperty(string name) : base(name) { }

			protected internal override VertexElement[] GetVertexDeclaration(short stream, ref byte usageIndex)
			{
				VertexElement[] result = new VertexElement[1];

				result[0] = new VertexElement(
					stream,
					0,
					VertexElementFormat.Single,
					VertexElementMethod.Default, 
					VertexElementUsage.TextureCoordinate, 
					usageIndex++);

				return result;
			}

			protected internal override void SetEffectParameterValue(EffectParameter parameter, int[] value)
			{
				parameter.SetValue(value);
			}
		}

		#endregion

		#region SingleInstancedDataProperty Declaration

		internal class SingleInstancedDataProperty : InstancedDataProperty<float>
		{
			internal SingleInstancedDataProperty(string name) : base(name) { }

			protected internal override VertexElement[] GetVertexDeclaration(short stream, ref byte usageIndex)
			{
				VertexElement[]  result = new VertexElement[1];

				result[0] = new VertexElement(
					stream,
					0,
					VertexElementFormat.Single,
					VertexElementMethod.Default,
					VertexElementUsage.TextureCoordinate,
					usageIndex++);

				return result;
			}

			protected internal override void SetEffectParameterValue(EffectParameter parameter, float[] value)
			{
				parameter.SetValue(value);
			}
		}

		#endregion

		#region MatrixInstancedDataProperty Declaration

		internal class MatrixInstancedDataProperty : InstancedDataProperty<Matrix>
		{
			private const short SizeOfVector4 = sizeof(float) * 4;

			internal MatrixInstancedDataProperty(string name) : base(name) { }

			protected internal override VertexElement[] GetVertexDeclaration(short stream, ref byte usageIndex)
			{
				VertexElement[] result = new VertexElement[4];

				short offset = 0;
				for (int i = 0; i < result.Length; i++)
				{
					result[i] = new VertexElement(
						stream,
						offset,
						VertexElementFormat.Vector4,
						VertexElementMethod.Default,
						VertexElementUsage.TextureCoordinate,
						usageIndex++);

					offset += MatrixInstancedDataProperty.SizeOfVector4;
				}

				return result;
			}

			protected internal override void SetEffectParameterValue(EffectParameter parameter, Matrix[] value)
			{
				parameter.SetValue(value);
			}
		}

		#endregion

		#region InstancedDatePropertyCollection Declaration

		public class InstancedDatePropertyCollection : KeyedCollection<string, InstancedDataProperty>
		{
			internal InstancedDatePropertyCollection() : base(StringComparer.OrdinalIgnoreCase) { }

			protected override string GetKeyForItem(InstancedDataProperty item)
			{
				return item.Name;
			}
		}

		#endregion

		private static Dictionary<Type, Type> propertyTypes = new Dictionary<Type, Type>();

		private int size;
		private InstancedDatePropertyCollection properties = new InstancedDatePropertyCollection();

		static InstancedData()
		{
			InstancedData.propertyTypes.Add(typeof(Quaternion), typeof(QuaternionInstancedDataProperty));
			InstancedData.propertyTypes.Add(typeof(Vector2), typeof(Vector2InstancedDataProperty));
			InstancedData.propertyTypes.Add(typeof(Vector3), typeof(Vector3InstancedDataProperty));
			InstancedData.propertyTypes.Add(typeof(Vector4), typeof(Vector4InstancedDataProperty));
			InstancedData.propertyTypes.Add(typeof(bool), typeof(BooleanInstancedDataProperty));
			InstancedData.propertyTypes.Add(typeof(int), typeof(Int32InstancedDataProperty));
			InstancedData.propertyTypes.Add(typeof(float), typeof(SingleInstancedDataProperty));
			InstancedData.propertyTypes.Add(typeof(Matrix), typeof(MatrixInstancedDataProperty));
		}

		protected InstancedData(int size)
		{
			this.size = size;
		}

		public int Size
		{
			get { return this.size; }
		}

		protected InstancedDataProperty<T> GetProperty<T>(string name) where T : struct
		{
			if (this.properties.Contains(name))
			{
				return this.properties[name] as InstancedDataProperty<T>;
			}

			Type type = InstancedData.propertyTypes[typeof(T)];

			ConstructorInfo constructorInfo = type.GetConstructor(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance, null, new Type[] { typeof(string) }, null);
			if (constructorInfo == null)
			{
				throw new InvalidOperationException();
			}

			InstancedDataProperty<T> property = constructorInfo.Invoke(new object[] { name }) as InstancedDataProperty<T>;
			property.SetValue(Array.CreateInstance(typeof(T), this.size));

			this.properties.Add(property);
			
			return property;
		}

		internal InstancedDatePropertyCollection Properties
		{
			get { return this.properties; }
		}
	}
}
